#include "StdAfx.h"
#include "Globals.h"
#include "Draw.h"
#include "MainFunctions.h"

using namespace PTT;

//-----------------------------------------------------------------------------
const OsuFile*		PreviewSong			= NULL;
const
GraphicsFile*		previewBG			= NULL;		// "PreviewSong" will be played and the background will be loaded and referenced here, when ready
CRITICAL_SECTION	previewBG_access;					// read and write previewBG access
bool					StopPreview			= true;		// stop playing preview file
bool					exitPreviewThread	= false;		// request thread to return

//-----------------------------------------------------------------------------
DWORD WINAPI PreviewThread(LPVOID lpParam)
{
	InitializeCriticalSection(&previewBG_access);

	LOG("PreviewThread start");

//	local objects
	{
		std::wstring	previewFilename	= L"";	
		std::wstring	previewGraphics	= L"";
		MediaFile		previewFile0(audio),
							previewFile1(audio);
		MediaFile*		previewFiles[2]	= {&previewFile0,&previewFile1};
		int				index					= 0;
		const OsuFile*	lastPreview			= NULL;

		previewFile0.setVolume(0.01f*SongVolume);
		previewFile1.setVolume(0.01f*SongVolume);

		SetThreadPriority(GetCurrentThread(), THREAD_MODE_BACKGROUND_BEGIN);

		GraphicsFile previewTextureData;

		LOG("PreviewThread starting main loop");

	//----
		while(!exitPreviewThread)
		{
			if(StopPreview)
			{
			//----
			//	hibernating mode: stop audio and sleep
				for(int i = 0; i < 2; i++)
				{
					if(previewFiles[i]->getVolume() <= 0.0f)
						previewFiles[i]->stop();
					else
						previewFiles[i]->fadeOut();
				}

				Sleep(20);
			}
			else
			{
			//----
			//	main working loop
				const OsuFile* curr = PreviewSong;			// we put "PreviewSong" into a local variable, to get around synchronization.
				if(lastPreview != curr && curr)
				{
					lastPreview = curr;
					StopSong = true;								// Tell the "InputThread" to fade out the last main song.

					if(curr->audioFilename.size())		previewFilename = curr->audioFilename;
					if(curr->backgroundFilename.size())	previewGraphics = curr->backgroundFilename;

					if(previewFilename.size())
					{
					// Switch between two streams
						index = !index;
						size_t len = previewFilename.size();
						previewFiles[index]->open(previewFilename);
						previewFiles[index]->seek(curr->previewTime, previewFilename[len-3]=='o'&&previewFilename[len-2]=='g'&&previewFilename[len-1]=='g');
						previewFiles[index]->play();
						previewFiles[index]->fadeIn();
						previewFilename.resize(0);
					}

					if(previewGraphics.size())
					{
						EnterCriticalSection(&previewBG_access);
						{
							previewTextureData.readFile(previewGraphics.c_str());
							previewBG = &previewTextureData;
						}
						LeaveCriticalSection(&previewBG_access);
						previewGraphics.resize(0);
					}
					else
						previewBG = defaultBG;
				}

				previewFiles[!index]->fadeOut();

			//----
			//	Sleep just a little.
				Sleep(5);
			}

		}//while loop

//----
//	local media files are destructed here
	}

	DeleteCriticalSection(&previewBG_access);

//----
//	let the main thread know we're finished
	exitPreviewThread = false;
	return 0;

}//SongPreviewThread


//-----------------------------------------------------------------------------
void ExitPreviewThread()
{
//	let the preview thread know it's no longer needed
	exitPreviewThread = true;

//----
//	wait for preview thread to exit
	while(exitPreviewThread)
		Sleep(1);
}



//-----------------------------------------------------------------------------
void SelectSong()
{
	static Texture previewTexture;

	Counter timer;

						PTT::BonusMode = false;
						StopPreview		= false;	// "wake up" PreviewThread
						SelectedSong	= NULL;
						PreviewSong		= NULL;
	static int		selectedIndex	= -1;		// remember selected song
	float				beat				= 0.0f;	// beat of the preview song in rad
	static float	bpm				= 200.0;	// beats per minute of the current song
	static float	scrollOffset	= 0.0f;	// remember scroll offset
	float				scrollSpeed		= 0.0f,
						angle				= 0.0f;
	const int		fontSize			= 28;
	const float		fontSizef		= fontSize,
						TWO_PI			= 6.28318530718f;
	float				keyCount			= 0;
	int				inputConfig		= 0;
	int				mode				= 0;
	RECT				window;
	LONG				wndW, wndH;
	POINT				lastCursor;
	GetCursorPos(&lastCursor);

	LOG("Starting main loop.");

	for(;;)
	{
	//----
		CURSORINFO cursorInfo = {sizeof(CURSORINFO)};
		GetCursorInfo(&cursorInfo);
		POINT &cursor = cursorInfo.ptScreenPos;
		LONG	cursorScreenX = cursor.x,
				cursorScreenY = cursor.y;
		bool	cursorVisible = cursorInfo.flags==0;

		GetClientRect (wnd->getHWND(), &window);
		ScreenToClient(wnd->getHWND(), &cursor);
		wndW = window.right;
		wndH = window.bottom;
	//----
	//	current input mode; state and cursor dependent; e.g., 0 = song select, 1 = difficulty select, 2 = input config
		if(mode < 3)
		{
			if(inputConfig || cursor.x >= wndW-200 && cursor.y <= 60)
				mode = 2;
			else
			if(selectedIndex>=0 && cursor.x >= wndW/2 && cursor.y >= wndH/2)
				mode = 1;
			else
				mode = 0;
		}

		glViewport(0, 0, wndW, wndH);
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		glOrtho(0, wndW, wndH, 0, -1.0, 1.0);
		glClearColor(0, 0, 0, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		white.glApply();

	//----
	//	load preview graphic
		if(previewBG)
		if(TryEnterCriticalSection(&previewBG_access))
		{
			previewTexture.setData(*previewBG);
			previewBG = NULL;

			LeaveCriticalSection(&previewBG_access);
		}

	//----
	//	display preview graphic
		previewTexture.draw(0.75f*wndW, 0.25f*wndH, min(1.0f,0.45f*wndH/previewTexture.getHeight()), true);

	//----
	//	draw song list
		float scrollLimit = wndH - (SongNames.size()+1)*fontSizef;
		if(scrollOffset < scrollLimit)
		{
			scrollOffset = scrollLimit;
			scrollSpeed  = 0;
		}
		if(scrollOffset > 0)
		{
			scrollOffset = 0;
			scrollSpeed  = 0;
		}

	//----
		float		t = (float)timer.getAndReset(),			// last frame time in seconds
					x = fontSizef,
					y = fontSizef*0.5f + scrollOffset;

		if(PreviewSong && PreviewSong->timingPoints.size())
			bpm = 60.0f / float(0.001*PreviewSong->timingPoints[0].msPerBeat);
		beat += TWO_PI*t*bpm/(4.0f*60.0f);
		if(beat  > TWO_PI)
			beat -= TWO_PI;

		float	rgb[3],
				hue = beat;

		int s = -1,								// selected index, below the cursor
			 sY = 0;								// y coordinate of selected song

		for(unsigned i = 0; i < SongNames.size(); i++, y+=fontSizef)
		{
			hue += 0.1f;
			if(hue  > TWO_PI)
				hue -= TWO_PI;

			if(y < -fontSizef)
				continue;						// don't render what's offscreen
			else if(y > wndH)
				break;							// don't render what's offscreen

			float lum = 0;

			if(cursorVisible)
			{
				lum = 50.0f*exp(-0.02f*fabs(cursor.y - y-0.5f*fontSizef));

				if(mode==0)
				if(s < 0 && cursor.y < y+0.8f*fontSizef)
				{
					s = i;
					lum = 200.0f;
				}
			}

			if(NameIndex[i]==selectedIndex)
			{
				lum = cos(3*hue);
				lum *= lum;
				ColorConversion::LCHABtoRGB(50.0f+50.0f*lum, 90.0f, 0.4f, &rgb[0]);
				sY = int(y+0.5f*fontSizef);
			}
			else
				ColorConversion::LCHABtoRGB(50.0f+lum, 70.0f+0.2f*lum, hue, &rgb[0]);

			FontColor(rgb[0], rgb[1], rgb[2]);

			DrawString(SongNames[NameIndex[i]], x, y-0.2f*fontSizef);
		}

	//----
	//	draw selected song info and select difficulty
		int selectedDifficulty = -1;
		if(selectedIndex >= 0)
		{
			float	x = 0.9f*wndW,
					x2= 0.6f*wndW;
					y = 0.5f*wndH;
			for(unsigned i = 0; i < OsuFiles[selectedIndex].size(); i++, y+=1.5f*fontSizef)
			{
				float a = 0.5f;
				if(cursorVisible && mode==1)
				if(selectedDifficulty < 0 && cursor.y <= y+1.5f*fontSizef)
				{
					selectedDifficulty = i;
					a = 1.0f;
				}
				OsuFile &o = *OsuFiles[selectedIndex][i];
				glColor4f(1.0f, 1.0f, 1.0f, a);
				if(o.overallDifficulty >= 0)
				{
					int	stars	= o.overallDifficulty/2 + 1,
							half	= o.overallDifficulty%2;
					DrawStars(stars, half, 5, x, y, true);
				}
				FixedFont->drawString(o.metadata.version, x2, y+0.5f*fontSizef);
			}
		}

	//----
	//	draw highscore
		DWORD highscore1 = 0, highscore2 = 0, great = 0;
		if(selectedDifficulty >= 0)
		{
			SelectedSong = OsuFiles[selectedIndex][selectedDifficulty];
			GetHighscore(highscore1, highscore2, &great);
			if			(great & 0x4)	glColor4f(1.0f, 0.4f, 1.0f, 1.0f);	// perfect
			else if	(great & 0x1)	glColor4f(1.0f, 0.6f, 0.1f, 1.0f);	// great (clear)
			else if	(highscore1)	glColor4f(0.1f, 0.6f, 1.0f, 1.0f);	// standard
			else							glColor4f(1.0f, 1.0f, 1.0f, 0.5f);
			FixedFont->drawString(L"Highscore", 0.6f*wndW, 0.9f*wndH, 1.0f, align::left,  align::bottom);
			FixedFont->drawNumber(highscore1,   0.9f*wndW, 0.9f*wndH, 1.0f, align::right, align::bottom, false, 7);
			if(great & 1)
			{
				if			(great & 0x8)	glColor4f(1.0f, 0.4f, 1.0f, 1.0f);	// perfect
				else if	(great & 0x2)	glColor4f(1.0f, 0.6f, 0.1f, 1.0f);	// great (clear)
				else if	(highscore2)	glColor4f(0.1f, 0.6f, 1.0f, 1.0f);	// standard
				else							glColor4f(1.0f, 1.0f, 1.0f, 0.5f);
				FixedFont->drawString(L"Highscore II", 0.6f*wndW, 0.95f*wndH, 1.0f, align::left,  align::bottom);
				FixedFont->drawNumber(highscore2,      0.9f*wndW, 0.95f*wndH, 1.0f, align::right, align::bottom, false, 7);
			}
		}


		if(mode==2)	glColor4ub(255,255,192,255);
		else			glColor4ub(255,255,192,192);
		FixedFont->drawString(L"setup keys", 0.98f*wndW, 0.02f*wndH, 1.0f, align::right);

	//----
	//	exit condition
		if(wnd->isQuitting())
			break;

	//----
	//	Input
		Input::KeyboardState key;
		Input::MouseState		mouse;
		Input::PadState		pad;
		Input::UpdateXInput();
		Input::GetAndReset(&key, &mouse, &pad);

	//----
		if(mode==3)
		{
			if(key.Return || mouse.LButton || pad.A)
			{
				PTT::BonusMode = cursor.x >= wndW/2;
				break;
			}

			if(key.Escape || pad.B)
			{
				key.Escape =					// else the application would quit!
				pad.B = false;					// else the cursor would reset!
				mode = 1;
			}
		}

		bool setCursorPos = false;			// set new cursor pos at the end

		if(key.Return||mouse.LButton||pad.A)
		if(mode == 1 && selectedDifficulty >= 0 && selectedIndex >= 0 && OsuFiles[selectedIndex].size())
		{
			SelectedSong = OsuFiles[selectedIndex][selectedDifficulty];
			if(great & 1)
			{
				mode = 3;						// goto bonus mode selction
				cursor.x = int(0.52f*wndW);
				cursor.y = int(0.55f*wndH);
				setCursorPos = true;
			}
			else
				break;
		}

	//----
	//	configure keyboard buttons
		if(mode==2 && mouse.LButton)
			inputConfig = 1;
		if(inputConfig)
		{
			glColor4ub(0,0,0,192);
			DrawRect(window);
			white.glApply();
			int button = inputConfig-1;
			Buttons2->draw(button, 0.5f*wndW, 0.5f*wndH, 1.0f, true);
			static const std::wstring buttonNames[4] = {L"circle",L"cross",L"square",L"triangle"};
			FixedFont->drawString(L"Press key for "+buttonNames[button]+L" button", 0.5f*wndW, 0.8f*wndH, 1.5f, align::centered);
			if(key.LastKey)
			{
				VK_BUTTONS[button] = key.LastKey;
				inputConfig++;
			}

			if(inputConfig > 4)
			{
				SaveButtons();
				inputConfig = 0;
			}
		}

	//----
		if(mode==3)
		{
			if(key.Left || pad.D_Left)
			{
				cursor.x -= 80;
				setCursorPos = true;
			}
			if(key.Right || pad.D_Right)
			{
				cursor.x += 80;
				setCursorPos = true;
			}

			glColor4ub(0,0,0,192);
			DrawRect(window);
			glColor4f(1.0f, 0.8f, 0.8f, 1.0f);
			FixedFont->drawString(L"Bonus Mode?", 0.5f*wndW, 0.5f*wndH, 1.5f, align::centered, align::bottom);
			if(cursor.x < wndW/2)	glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
			else							glColor4f(1.0f, 1.0f, 1.0f, 0.6f);
			FixedFont->drawString(L"NO",  0.48f*wndW, 0.52f*wndH, 1.5f, align::right);
			if(cursor.x >= wndW/2)	glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
			else							glColor4f(1.0f, 1.0f, 1.0f, 0.6f);
			FixedFont->drawString(L"YES", 0.52f*wndW, 0.52f*wndH, 1.5f, align::left);
		}

	//----
	//	update cursor angle, and draw cursor if visible
		{
			PixelColorReset();
			float dx = float(cursor.x-lastCursor.x),
					dy = float(cursor.y-lastCursor.y),
					d  = sqrt(dx*dx+dy*dy),
					f	= 1.0f - exp(-0.02f*d);
//			Spinor newDir(-dy,dx);
			float	newAngle = 20.8f + 90.0f + 360.0f / TWO_PI * atan2(dy,dx),
					difAngle = newAngle - angle;
			while(difAngle < -180.0f)	difAngle += 360.0f;
			while(difAngle >  180.0f)	difAngle -= 360.0f;
			angle += f*difAngle*((200.0f-fabs(difAngle))/180.0f);
			lastCursor = cursor;
			if(cursorVisible)
			{
				if(angle < -180.0f)	angle += 360.0f;
				if(angle >  180.0f)	angle -= 360.0f;
				float lum = sinf(beat);
				lum*=lum;
				lum*=0.5f;
				lum+=0.5f;
				glColor4f(lum, lum, lum, 1.0f);
				glPushMatrix();
				glTranslatef((float)cursor.x, (float)cursor.y, 0.0f);
				glRotatef(angle, 0.0f, 0.0f, 1.0f);
				glTranslatef(-40.0f, -20.0f, 0.0f);			// cursor hit point
				texCursor->draw();
				glPopMatrix();
			}
		}

		glFlush();
		SwapBuffers(wnd->getDC());

	//----
	//	exit
		if(key.Escape || pad.Back)
		{
			wnd->quit();
			break;
		}

	//----
	//	by holding down X, and scrolling through the list, you can stress test the preview thread...
		if((key.Return||key.Right||mouse.LButton||pad.A||pad.State.Gamepad.wButtons&XINPUT_GAMEPAD_X) && s >= 0 && OsuFiles[NameIndex[s]].size())
		{
		//----
		//	select file and start preview
			selectedIndex = NameIndex[s];
			PreviewSong	= OsuFiles[selectedIndex][max(selectedDifficulty,0)];

			if(mode==0 && (pad.A||key.Return||key.Right))
			{
				cursor.x = int(0.58f*wndW);
				cursor.y = int(0.5f*wndH+fontSizef);
				angle = 0;
				lastCursor.x = 0;				// to avoid recalculating the angle
				lastCursor.y = 0;
				setCursorPos = true;
			}
		}

	//----
	//	back
		if(mode<=1 && (pad.B||key.Left))
		{
			cursor.x = int(0.4f*wndW);
			cursor.y = sY;
			setCursorPos = true;
		}

	//----
	//	thumbsticks
		if(pad.LThumb.x)	cursor.x += int(2.0e3f*t*pad.LThumb.x);
		if(pad.LThumb.y)	cursor.y -= int(2.0e3f*t*pad.LThumb.y);
		if(pad.RThumb.y)	scrollOffset += float(100.0*fontSizef*t*pad.RThumb.y);

		if(mouse.ScrollUp)	{	scrollSpeed += 40.0f*fontSizef;	}
		if(mouse.ScrollDown)	{	scrollSpeed -= 40.0f*fontSizef;	}

	//----
	//	Jump to first letter
		if(key.LastKey >= 'a' && key.LastKey <= 'z')
		{
			int searchLetter = key.LastKey;
			while(tolower(SongNames[NameIndex[s]][0]) < searchLetter && s < int(SongNames.size())-1 && tolower(SongNames[NameIndex[s+1]][0]) <= searchLetter)	{	scrollOffset -= fontSizef;	s++;	}
			while(tolower(SongNames[NameIndex[s]][0]) > searchLetter && s > 0                       && tolower(SongNames[NameIndex[s-1]][0]) >= searchLetter)	{	scrollOffset += fontSizef;	s--;	}
			scrollSpeed = 0;
		}

	//----
		if			(scrollSpeed > 0)	scrollSpeed = max(0.0f, scrollSpeed-t*(0.002f*sqr(scrollSpeed)+abs(scrollSpeed)+500.0f));
		else if	(scrollSpeed < 0)	scrollSpeed = min(0.0f, scrollSpeed+t*(0.002f*sqr(scrollSpeed)+abs(scrollSpeed)+500.0f));

		scrollOffset += t*scrollSpeed;

		bool	kUp	= key.Up   || (pad.State.Gamepad.wButtons&XINPUT_GAMEPAD_DPAD_UP),
				kDown	= key.Down || (pad.State.Gamepad.wButtons&XINPUT_GAMEPAD_DPAD_DOWN);
		if(kUp || kDown)
		{
			int dy = mode == 0 ? fontSize : 3*fontSize/2;
			if(keyCount == 0 || keyCount > 0.2f || key.Up || key.Down)
			{
				if(kUp)	cursor.y -= dy;
				else		cursor.y += dy;
			}
			keyCount += t;
		}
		else
		{
			keyCount = 0;
		}

		if(kUp || kDown || pad.LThumb.x || pad.LThumb.y)
		{
			if(cursor.x < fontSize)
				cursor.x = fontSize;
			if(cursor.x > wndW-fontSize)
				cursor.x = wndW-fontSize;
			if(cursor.y < fontSize/2)
			{
				cursor.y = fontSize/2;
				scrollOffset += fontSizef;
			}
			else if(cursor.y > wndH-fontSize+fontSize/8)
			{
				cursor.y = wndH-fontSize+fontSize/8;
				scrollOffset -= fontSizef;
			}
			setCursorPos = true;
		}

		if(setCursorPos)
		{
			ClientToScreen(wnd->getHWND(), &cursor);
			SetCursorPos(cursor.x, cursor.y);
		}

		UpdateCaption();
	}

	PixelColorReset();

	glColor4ub(0,0,0,200);
	DrawRect(window);
	white.glApply();
	FixedFont->drawString(L"PLEASE WAIT", 0.5f*wndW, 0.5f*wndH, 1.0f, align::centered, align::center);

	glFlush();
	SwapBuffers(wnd->getDC());

}//SelectSong


